package scw.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

import scw.lang.NamedThreadLocal;

public final class Bytes {
	private Bytes() {
	};

	private static final String C64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; // default
																											// base64.

	private static final char[] BASE16 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
			'f' }, BASE64 = C64.toCharArray();

	private static final int MASK4 = 0x0f, MASK6 = 0x3f, MASK8 = 0xff;

	private static final Map<Integer, byte[]> DECODE_TABLE_MAP = new ConcurrentHashMap<Integer, byte[]>();

	private static ThreadLocal<MessageDigest> MD = new NamedThreadLocal<MessageDigest>(Bytes.class.getSimpleName());

	/**
	 * byte array copy.
	 * 
	 * @param src
	 *            src.
	 * @param length
	 *            new length.
	 * @return new byte array.
	 */
	public static byte[] copyOf(byte[] src, int length) {
		byte[] dest = new byte[length];
		System.arraycopy(src, 0, dest, 0, Math.min(src.length, length));
		return dest;
	}

	public static List<byte[]> string2byteList(Collection<String> strings) {
		if (strings == null) {
			return null;
		}

		List<byte[]> list = new ArrayList<byte[]>(strings.size());
		Iterator<String> iterator = strings.iterator();
		while (iterator.hasNext()) {
			String v = iterator.next();
			if (v == null) {
				list.add(null);
				continue;
			}

			list.add(string2bytes(v));
		}
		return list;
	}

	public static byte[][] string2bytes(Collection<String> strings) {
		if (strings == null) {
			return null;
		}

		byte[][] bs = new byte[strings.size()][];
		int i = 0;
		Iterator<String> iterator = strings.iterator();
		while (iterator.hasNext()) {
			String v = iterator.next();
			bs[i++] = string2bytes(v);
		}
		return bs;
	}

	public static byte[] string2bytes(String string) {
		if (string == null) {
			return null;
		}

		int size = string.length();
		byte[] bytes = new byte[size * 2];
		for (int i = 0, b = 0; i < size; i++, b += 2) {
			Bits.putChar(bytes, b, string.charAt(i));
		}
		return bytes;
	}

	public static String bytes2String(byte[] b) {
		if (b == null) {
			return null;
		}

		return new String(bytes2chars(b));
	}

	public static byte[][] string2bytes(String... strings) {
		if (strings == null) {
			return null;
		}

		byte[][] bs = new byte[strings.length][];
		for (int i = 0; i < strings.length; i++) {
			bs[i] = string2bytes(strings[i]);
		}
		return bs;
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @return byte[].
	 */
	public static byte[] short2bytes(short v) {
		byte[] ret = { 0, 0 };
		short2bytes(v, ret);
		return ret;
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 */
	public static void short2bytes(short v, byte[] b) {
		short2bytes(v, b, 0);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 */
	public static void short2bytes(short v, byte[] b, int off) {
		b[off + 1] = (byte) v;
		b[off + 0] = (byte) (v >>> 8);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @return byte[].
	 */
	public static byte[] int2bytes(int v) {
		byte[] ret = { 0, 0, 0, 0 };
		int2bytes(v, ret);
		return ret;
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 */
	public static void int2bytes(int v, byte[] b) {
		int2bytes(v, b, 0);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 * @param off
	 *            array offset.
	 */
	public static void int2bytes(int v, byte[] b, int off) {
		b[off + 3] = (byte) v;
		b[off + 2] = (byte) (v >>> 8);
		b[off + 1] = (byte) (v >>> 16);
		b[off + 0] = (byte) (v >>> 24);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @return byte[].
	 */
	public static byte[] float2bytes(float v) {
		byte[] ret = { 0, 0, 0, 0 };
		float2bytes(v, ret);
		return ret;
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 */
	public static void float2bytes(float v, byte[] b) {
		float2bytes(v, b, 0);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 * @param off
	 *            array offset.
	 */
	public static void float2bytes(float v, byte[] b, int off) {
		int i = Float.floatToIntBits(v);
		b[off + 3] = (byte) i;
		b[off + 2] = (byte) (i >>> 8);
		b[off + 1] = (byte) (i >>> 16);
		b[off + 0] = (byte) (i >>> 24);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @return byte[].
	 */
	public static byte[] long2bytes(long v) {
		byte[] ret = { 0, 0, 0, 0, 0, 0, 0, 0 };
		long2bytes(v, ret);
		return ret;
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 */
	public static void long2bytes(long v, byte[] b) {
		long2bytes(v, b, 0);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 * @param off
	 *            array offset.
	 */
	public static void long2bytes(long v, byte[] b, int off) {
		b[off + 7] = (byte) v;
		b[off + 6] = (byte) (v >>> 8);
		b[off + 5] = (byte) (v >>> 16);
		b[off + 4] = (byte) (v >>> 24);
		b[off + 3] = (byte) (v >>> 32);
		b[off + 2] = (byte) (v >>> 40);
		b[off + 1] = (byte) (v >>> 48);
		b[off + 0] = (byte) (v >>> 56);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @return byte[].
	 */
	public static byte[] double2bytes(double v) {
		byte[] ret = { 0, 0, 0, 0, 0, 0, 0, 0 };
		double2bytes(v, ret);
		return ret;
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 */
	public static void double2bytes(double v, byte[] b) {
		double2bytes(v, b, 0);
	}

	/**
	 * to byte array.
	 * 
	 * @param v
	 *            value.
	 * @param b
	 *            byte array.
	 * @param off
	 *            array offset.
	 */
	public static void double2bytes(double v, byte[] b, int off) {
		long j = Double.doubleToLongBits(v);
		b[off + 7] = (byte) j;
		b[off + 6] = (byte) (j >>> 8);
		b[off + 5] = (byte) (j >>> 16);
		b[off + 4] = (byte) (j >>> 24);
		b[off + 3] = (byte) (j >>> 32);
		b[off + 2] = (byte) (j >>> 40);
		b[off + 1] = (byte) (j >>> 48);
		b[off + 0] = (byte) (j >>> 56);
	}

	/**
	 * to short.
	 * 
	 * @param b
	 *            byte array.
	 * @return short.
	 */
	public static short bytes2short(byte[] b) {
		return bytes2short(b, 0);
	}

	/**
	 * to short.
	 * 
	 * @param b
	 *            byte array.
	 * @param off
	 *            offset.
	 * @return short.
	 */
	public static short bytes2short(byte[] b, int off) {
		return (short) (((b[off + 1] & 0xFF) << 0) + ((b[off + 0]) << 8));
	}

	/**
	 * to int.
	 * 
	 * @param b
	 *            byte array.
	 * @return int.
	 */
	public static int bytes2int(byte[] b) {
		return bytes2int(b, 0);
	}

	/**
	 * to int.
	 * 
	 * @param b
	 *            byte array.
	 * @param off
	 *            offset.
	 * @return int.
	 */
	public static int bytes2int(byte[] b, int off) {
		return ((b[off + 3] & 0xFF) << 0) + ((b[off + 2] & 0xFF) << 8) + ((b[off + 1] & 0xFF) << 16)
				+ ((b[off + 0]) << 24);
	}

	/**
	 * to int.
	 * 
	 * @param b
	 *            byte array.
	 * @return int.
	 */
	public static float bytes2float(byte[] b) {
		return bytes2float(b, 0);
	}

	/**
	 * to int.
	 * 
	 * @param b
	 *            byte array.
	 * @param off
	 *            offset.
	 * @return int.
	 */
	public static float bytes2float(byte[] b, int off) {
		int i = ((b[off + 3] & 0xFF) << 0) + ((b[off + 2] & 0xFF) << 8) + ((b[off + 1] & 0xFF) << 16)
				+ ((b[off + 0]) << 24);
		return Float.intBitsToFloat(i);
	}

	/**
	 * to long.
	 * 
	 * @param b
	 *            byte array.
	 * @return long.
	 */
	public static long bytes2long(byte[] b) {
		return bytes2long(b, 0);
	}

	/**
	 * to long.
	 * 
	 * @param b
	 *            byte array.
	 * @param off
	 *            offset.
	 * @return long.
	 */
	public static long bytes2long(byte[] b, int off) {
		return ((b[off + 7] & 0xFFL) << 0) + ((b[off + 6] & 0xFFL) << 8) + ((b[off + 5] & 0xFFL) << 16)
				+ ((b[off + 4] & 0xFFL) << 24) + ((b[off + 3] & 0xFFL) << 32) + ((b[off + 2] & 0xFFL) << 40)
				+ ((b[off + 1] & 0xFFL) << 48) + (((long) b[off + 0]) << 56);
	}

	/**
	 * to long.
	 * 
	 * @param b
	 *            byte array.
	 * @return double.
	 */
	public static double bytes2double(byte[] b) {
		return bytes2double(b, 0);
	}

	/**
	 * to long.
	 * 
	 * @param b
	 *            byte array.
	 * @param off
	 *            offset.
	 * @return double.
	 */
	public static double bytes2double(byte[] b, int off) {
		long j = ((b[off + 7] & 0xFFL) << 0) + ((b[off + 6] & 0xFFL) << 8) + ((b[off + 5] & 0xFFL) << 16)
				+ ((b[off + 4] & 0xFFL) << 24) + ((b[off + 3] & 0xFFL) << 32) + ((b[off + 2] & 0xFFL) << 40)
				+ ((b[off + 1] & 0xFFL) << 48) + (((long) b[off + 0]) << 56);
		return Double.longBitsToDouble(j);
	}

	public static char[] bytes2chars(byte[] b) {
		if ((b.length & 1) != 0) {
			throw new IllegalArgumentException("一个char占用两个byte，所以输入的byte数组长度必须是一个偶数");
		}

		char[] chars = new char[b.length >> 1];
		for (int i = 0, index = 0; i < b.length; i += 2, index++) {
			chars[index] = Bits.getChar(b, i);
		}
		return chars;
	}

	/**
	 * to hex string.
	 * 
	 * @param bs
	 *            byte array.
	 * @return hex string.
	 */
	public static String bytes2hex(byte[] bs) {
		return bytes2hex(bs, 0, bs.length);
	}

	/**
	 * to hex string.
	 * 
	 * @param bs
	 *            byte array.
	 * @param off
	 *            offset.
	 * @param len
	 *            length.
	 * @return hex string.
	 */
	public static String bytes2hex(byte[] bs, int off, int len) {
		if (off < 0)
			throw new IndexOutOfBoundsException("bytes2hex: offset < 0, offset is " + off);
		if (len < 0)
			throw new IndexOutOfBoundsException("bytes2hex: length < 0, length is " + len);
		if (off + len > bs.length)
			throw new IndexOutOfBoundsException("bytes2hex: offset + length > array length.");

		byte b;
		int r = off, w = 0;
		char[] cs = new char[len * 2];
		for (int i = 0; i < len; i++) {
			b = bs[r++];
			cs[w++] = BASE16[b >> 4 & MASK4];
			cs[w++] = BASE16[b & MASK4];
		}
		return new String(cs);
	}

	/**
	 * from hex string.
	 * 
	 * @param str
	 *            hex string.
	 * @return byte array.
	 */
	public static byte[] hex2bytes(String str) {
		return hex2bytes(str, 0, str.length());
	}

	/**
	 * from hex string.
	 * 
	 * @param str
	 *            hex string.
	 * @param off
	 *            offset.
	 * @param len
	 *            length.
	 * @return byte array.
	 */
	public static byte[] hex2bytes(final String str, final int off, int len) {
		if ((len & 1) == 1)
			throw new IllegalArgumentException("hex2bytes: ( len & 1 ) == 1.");

		if (off < 0)
			throw new IndexOutOfBoundsException("hex2bytes: offset < 0, offset is " + off);
		if (len < 0)
			throw new IndexOutOfBoundsException("hex2bytes: length < 0, length is " + len);
		if (off + len > str.length())
			throw new IndexOutOfBoundsException("hex2bytes: offset + length > array length.");

		int num = len / 2, r = off, w = 0;
		byte[] b = new byte[num];
		for (int i = 0; i < num; i++)
			b[w++] = (byte) (hex(str.charAt(r++)) << 4 | hex(str.charAt(r++)));
		return b;
	}

	/**
	 * to base64 string.
	 * 
	 * @param b
	 *            byte array.
	 * @return base64 string.
	 */
	public static String bytes2base64(byte[] b) {
		return bytes2base64(b, 0, b.length, BASE64);
	}

	/**
	 * to base64 string.
	 * 
	 * @param b
	 *            byte array.
	 * @return base64 string.
	 */
	public static String bytes2base64(byte[] b, int offset, int length) {
		return bytes2base64(b, offset, length, BASE64);
	}

	/**
	 * to base64 string.
	 * 
	 * @param b
	 *            byte array.
	 * @param code
	 *            base64 code string(0-63 is base64 char,64 is pad char).
	 * @return base64 string.
	 */
	public static String bytes2base64(byte[] b, String code) {
		return bytes2base64(b, 0, b.length, code);
	}

	/**
	 * to base64 string.
	 * 
	 * @param b
	 *            byte array.
	 * @param code
	 *            base64 code string(0-63 is base64 char,64 is pad char).
	 * @return base64 string.
	 */
	public static String bytes2base64(byte[] b, int offset, int length, String code) {
		if (code.length() < 64)
			throw new IllegalArgumentException("Base64 code length < 64.");

		return bytes2base64(b, offset, length, code.toCharArray());
	}

	/**
	 * to base64 string.
	 * 
	 * @param b
	 *            byte array.
	 * @param code
	 *            base64 code(0-63 is base64 char,64 is pad char).
	 * @return base64 string.
	 */
	public static String bytes2base64(byte[] b, char[] code) {
		return bytes2base64(b, 0, b.length, code);
	}

	/**
	 * to base64 string.
	 * 
	 * @param bs
	 *            byte array.
	 * @param off
	 *            offset.
	 * @param len
	 *            length.
	 * @param code
	 *            base64 code(0-63 is base64 char,64 is pad char).
	 * @return base64 string.
	 */
	public static String bytes2base64(final byte[] bs, final int off, final int len, final char[] code) {
		if (off < 0)
			throw new IndexOutOfBoundsException("bytes2base64: offset < 0, offset is " + off);
		if (len < 0)
			throw new IndexOutOfBoundsException("bytes2base64: length < 0, length is " + len);
		if (off + len > bs.length)
			throw new IndexOutOfBoundsException("bytes2base64: offset + length > array length.");

		if (code.length < 64)
			throw new IllegalArgumentException("Base64 code length < 64.");

		boolean pad = code.length > 64; // has pad char.
		int num = len / 3, rem = len % 3, r = off, w = 0;
		char[] cs = new char[num * 4 + (rem == 0 ? 0 : pad ? 4 : rem + 1)];

		for (int i = 0; i < num; i++) {
			int b1 = bs[r++] & MASK8, b2 = bs[r++] & MASK8, b3 = bs[r++] & MASK8;

			cs[w++] = code[b1 >> 2];
			cs[w++] = code[(b1 << 4) & MASK6 | (b2 >> 4)];
			cs[w++] = code[(b2 << 2) & MASK6 | (b3 >> 6)];
			cs[w++] = code[b3 & MASK6];
		}

		if (rem == 1) {
			int b1 = bs[r++] & MASK8;
			cs[w++] = code[b1 >> 2];
			cs[w++] = code[(b1 << 4) & MASK6];
			if (pad) {
				cs[w++] = code[64];
				cs[w++] = code[64];
			}
		} else if (rem == 2) {
			int b1 = bs[r++] & MASK8, b2 = bs[r++] & MASK8;
			cs[w++] = code[b1 >> 2];
			cs[w++] = code[(b1 << 4) & MASK6 | (b2 >> 4)];
			cs[w++] = code[(b2 << 2) & MASK6];
			if (pad)
				cs[w++] = code[64];
		}
		return new String(cs);
	}

	/**
	 * from base64 string.
	 * 
	 * @param str
	 *            base64 string.
	 * @return byte array.
	 */
	public static byte[] base642bytes(String str) {
		return base642bytes(str, 0, str.length());
	}

	/**
	 * from base64 string.
	 * 
	 * @param str
	 *            base64 string.
	 * @param offset
	 *            offset.
	 * @param length
	 *            length.
	 * @return byte array.
	 */
	public static byte[] base642bytes(String str, int offset, int length) {
		return base642bytes(str, offset, length, C64);
	}

	/**
	 * from base64 string.
	 * 
	 * @param str
	 *            base64 string.
	 * @param code
	 *            base64 code(0-63 is base64 char,64 is pad char).
	 * @return byte array.
	 */
	public static byte[] base642bytes(String str, String code) {
		return base642bytes(str, 0, str.length(), code);
	}

	/**
	 * from base64 string.
	 * 
	 * @param str
	 *            base64 string.
	 * @param off
	 *            offset.
	 * @param len
	 *            length.
	 * @param code
	 *            base64 code(0-63 is base64 char,64 is pad char).
	 * @return byte array.
	 */
	public static byte[] base642bytes(final String str, final int off, final int len, final String code) {
		if (off < 0)
			throw new IndexOutOfBoundsException("base642bytes: offset < 0, offset is " + off);
		if (len < 0)
			throw new IndexOutOfBoundsException("base642bytes: length < 0, length is " + len);
		if (off + len > str.length())
			throw new IndexOutOfBoundsException("base642bytes: offset + length > string length.");

		if (code.length() < 64)
			throw new IllegalArgumentException("Base64 code length < 64.");

		int rem = len % 4;
		if (rem == 1)
			throw new IllegalArgumentException("base642bytes: base64 string length % 4 == 1.");

		int num = len / 4, size = num * 3;
		if (code.length() > 64) {
			if (rem != 0)
				throw new IllegalArgumentException("base642bytes: base64 string length error.");

			char pc = code.charAt(64);
			if (str.charAt(off + len - 2) == pc) {
				size -= 2;
				--num;
				rem = 2;
			} else if (str.charAt(off + len - 1) == pc) {
				size--;
				--num;
				rem = 3;
			}
		} else {
			if (rem == 2)
				size++;
			else if (rem == 3)
				size += 2;
		}

		int r = off, w = 0;
		byte[] b = new byte[size], t = decodeTable(code);
		for (int i = 0; i < num; i++) {
			int c1 = t[str.charAt(r++)], c2 = t[str.charAt(r++)];
			int c3 = t[str.charAt(r++)], c4 = t[str.charAt(r++)];

			b[w++] = (byte) ((c1 << 2) | (c2 >> 4));
			b[w++] = (byte) ((c2 << 4) | (c3 >> 2));
			b[w++] = (byte) ((c3 << 6) | c4);
		}

		if (rem == 2) {
			int c1 = t[str.charAt(r++)], c2 = t[str.charAt(r++)];

			b[w++] = (byte) ((c1 << 2) | (c2 >> 4));
		} else if (rem == 3) {
			int c1 = t[str.charAt(r++)], c2 = t[str.charAt(r++)], c3 = t[str.charAt(r++)];

			b[w++] = (byte) ((c1 << 2) | (c2 >> 4));
			b[w++] = (byte) ((c2 << 4) | (c3 >> 2));
		}
		return b;
	}

	/**
	 * from base64 string.
	 * 
	 * @param str
	 *            base64 string.
	 * @param code
	 *            base64 code(0-63 is base64 char,64 is pad char).
	 * @return byte array.
	 */
	public static byte[] base642bytes(String str, char[] code) {
		return base642bytes(str, 0, str.length(), code);
	}

	/**
	 * from base64 string.
	 * 
	 * @param str
	 *            base64 string.
	 * @param off
	 *            offset.
	 * @param len
	 *            length.
	 * @param code
	 *            base64 code(0-63 is base64 char,64 is pad char).
	 * @return byte array.
	 */
	public static byte[] base642bytes(final String str, final int off, final int len, final char[] code) {
		if (off < 0)
			throw new IndexOutOfBoundsException("base642bytes: offset < 0, offset is " + off);
		if (len < 0)
			throw new IndexOutOfBoundsException("base642bytes: length < 0, length is " + len);
		if (off + len > str.length())
			throw new IndexOutOfBoundsException("base642bytes: offset + length > string length.");

		if (code.length < 64)
			throw new IllegalArgumentException("Base64 code length < 64.");

		int rem = len % 4;
		if (rem == 1)
			throw new IllegalArgumentException("base642bytes: base64 string length % 4 == 1.");

		int num = len / 4, size = num * 3;
		if (code.length > 64) {
			if (rem != 0)
				throw new IllegalArgumentException("base642bytes: base64 string length error.");

			char pc = code[64];
			if (str.charAt(off + len - 2) == pc)
				size -= 2;
			else if (str.charAt(off + len - 1) == pc)
				size--;
		} else {
			if (rem == 2)
				size++;
			else if (rem == 3)
				size += 2;
		}

		int r = off, w = 0;
		byte[] b = new byte[size];
		for (int i = 0; i < num; i++) {
			int c1 = indexOf(code, str.charAt(r++)), c2 = indexOf(code, str.charAt(r++));
			int c3 = indexOf(code, str.charAt(r++)), c4 = indexOf(code, str.charAt(r++));

			b[w++] = (byte) ((c1 << 2) | (c2 >> 4));
			b[w++] = (byte) ((c2 << 4) | (c3 >> 2));
			b[w++] = (byte) ((c3 << 6) | c4);
		}

		if (rem == 2) {
			int c1 = indexOf(code, str.charAt(r++)), c2 = indexOf(code, str.charAt(r++));

			b[w++] = (byte) ((c1 << 2) | (c2 >> 4));
		} else if (rem == 3) {
			int c1 = indexOf(code, str.charAt(r++)), c2 = indexOf(code, str.charAt(r++)),
					c3 = indexOf(code, str.charAt(r++));

			b[w++] = (byte) ((c1 << 2) | (c2 >> 4));
			b[w++] = (byte) ((c2 << 4) | (c3 >> 2));
		}
		return b;
	}

	/**
	 * zip.
	 * 
	 * @param bytes
	 *            source.
	 * @return compressed byte array.
	 * @throws IOException.
	 */
	public static byte[] zip(byte[] bytes) throws IOException {
		UnsafeByteArrayOutputStream bos = new UnsafeByteArrayOutputStream();
		OutputStream os = new DeflaterOutputStream(bos);
		try {
			os.write(bytes);
		} finally {
			os.close();
			bos.close();
		}
		return bos.toByteArray();
	}

	/**
	 * unzip.
	 * 
	 * @param bytes
	 *            compressed byte array.
	 * @return byte uncompressed array.
	 * @throws IOException
	 */
	public static byte[] unzip(byte[] bytes) throws IOException {
		UnsafeByteArrayInputStream bis = new UnsafeByteArrayInputStream(bytes);
		UnsafeByteArrayOutputStream bos = new UnsafeByteArrayOutputStream();
		InputStream is = new InflaterInputStream(bis);
		try {
			IOUtils.copy(is, bos);
			return bos.toByteArray();
		} finally {
			is.close();
			bis.close();
			bos.close();
		}
	}

	/**
	 * get md5.
	 * 
	 * @param source
	 *            byte array source.
	 * @return MD5 byte array.
	 */
	public static byte[] getMD5(byte[] source) {
		MessageDigest md = getMessageDigest();
		return md.digest(source);
	}

	/**
	 * get md5.
	 * 
	 * @param file
	 *            file source.
	 * @return MD5 byte array.
	 */
	public static byte[] getMD5(File file) throws IOException {
		InputStream is = new FileInputStream(file);
		try {
			return getMD5(is);
		} finally {
			is.close();
		}
	}

	/**
	 * get md5.
	 * 
	 * @param is
	 *            input stream.
	 * @return MD5 byte array.
	 */
	public static byte[] getMD5(InputStream is) throws IOException {
		return getMD5(is, 1024 * 8);
	}

	private static byte hex(char c) {
		if (c <= '9')
			return (byte) (c - '0');
		if (c >= 'a' && c <= 'f')
			return (byte) (c - 'a' + 10);
		if (c >= 'A' && c <= 'F')
			return (byte) (c - 'A' + 10);
		throw new IllegalArgumentException("hex string format error [" + c + "].");
	}

	private static int indexOf(char[] cs, char c) {
		for (int i = 0, len = cs.length; i < len; i++)
			if (cs[i] == c)
				return i;
		return -1;
	}

	private static byte[] decodeTable(String code) {
		int hash = code.hashCode();
		byte[] ret = DECODE_TABLE_MAP.get(hash);
		if (ret == null) {
			if (code.length() < 64)
				throw new IllegalArgumentException("Base64 code length < 64.");
			// create new decode table.
			ret = new byte[128];
			for (int i = 0; i < 128; i++) // init table.
				ret[i] = -1;
			for (int i = 0; i < 64; i++)
				ret[code.charAt(i)] = (byte) i;
			DECODE_TABLE_MAP.put(hash, ret);
		}
		return ret;
	}

	private static byte[] getMD5(InputStream is, int bs) throws IOException {
		MessageDigest md = getMessageDigest();
		byte[] buf = new byte[bs];
		while (is.available() > 0) {
			int read, total = 0;
			do {
				if ((read = is.read(buf, total, bs - total)) <= 0)
					break;
				total += read;
			} while (total < bs);
			md.update(buf);
		}
		return md.digest();
	}

	private static MessageDigest getMessageDigest() {
		MessageDigest ret = MD.get();
		if (ret == null) {
			try {
				ret = MessageDigest.getInstance("MD5");
				MD.set(ret);
			} catch (NoSuchAlgorithmException e) {
				throw new RuntimeException(e);
			}
		}
		return ret;
	}
}
